	TITLE	'XDIR Ver 1.0 for CP/M-86'
;
; XDIR.A86 for CP/M-86, as of February 18, 1981: Kelly Smith
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
;USER OPTION SPECIFICATIONS
;
AOPT	EQU	TRUE			;TRUE TO ALLOW SEARCHING ALL USER AREAS
DOPT	EQU	TRUE			;TRUE TO ALLOW SEARCHING ALL DRIVES ON-LINE
FOPT	EQU	TRUE			;TRUE TO ALLOW FILE OUTPUT OPTION
NOPT	EQU	TRUE			;TRUE TO ALLOW DISABLING PAGE PAUSE OPTION
PGPAWZ	EQU	TRUE			;TRUE FOR PAUSE AFTER EACH PAGE
POPT	EQU	TRUE			;TRUE TO ALLOW PRINTER OPTION
REPERR	EQU	TRUE			;TRUE TO REPORT COMMAND LINE OPTION ERRORS
REPSIZ	EQU	TRUE			;TRUE TO REPORT FILE SIZES
SOPT	EQU	TRUE			;TRUE TO ALLOW SYSTEM FILE OPTION
UOPT	EQU	TRUE			;TRUE TO ALLOW USER NUMBER OPTION
DELIM	EQU	7CH			;FENCE (DELIMITER) CHARACTER (VERTICAL BAR)
BIGCRT	EQU	TRUE			;TRUE = 24/80 DISPLAY, FALSE = 16/64 DISPLAY
NPL	EQU	4			;# OF NAMES PER LINE (MAX OF 3 FOR 64X16)
;						     (MAX OF 4 FOR 80X24)
LPS	EQU	20			;# OF LINES PER SCREEN (MAX OF 12 FOR 64X16)
;						       (MAX OF 20 FOR 80X24)
;
; BDOS EQUATES
;
RDCHR	EQU	1			;READ CHAR FROM	CONSOLE
WRCHR	EQU	2			;WRITE CHAR TO CONSOLE
CONST	EQU	11			;CHECK CONS STAT
RESET	EQU	13			;RESET DISK SYSTEM
SELDSK	EQU	14			;SELECT	DISK
OPEN	EQU	15			;0FFH=NOT FOUND
CLOSE	EQU	16			;   "	"
SEARCH	EQU	17			;   "	"
NEXT	EQU	18			;   "	"
READ	EQU	20			;NOT 0 = EOF
WRITE	EQU	21			;NOT 0 = DISK FULL
MAKE	EQU	22			;0FFH =	DIRECTORY FULL
CURDSK	EQU	25			;GET CURRENTLY LOGGED DISK NAME
SETDMA	EQU	26			;SET CURRENT DMA
GALLOC	EQU	27			;GET ADDRESS OF	ALLOCATION VECTOR
CURDPB	EQU	31			;GET CURRENT DISK PARAMETERS
CURUSR	EQU	32			;GET CURRENTLY LOGGED USER NUMBER
BASE	EQU	0
TPA	EQU	100H
FCB	EQU	BASE+5CH
;
M	EQU	Byte Ptr 0[BX]
;
; BEGIN	EXECUTABLE PROGRAM CODE
;
	ORG	TPA
START:	MOV	DL,0FFH			;GET CURRENT USER NUMBER
	MOV	CL,CURUSR
	CALL	CPM
	MOV	Byte Ptr OLDUSR,AL	;INITIALIZE STARTUP USER NUMBER
	MOV	Byte Ptr NEWUSR,AL	;..AND MAKE NEW	USER MATCH IT
	MOV	Byte Ptr BASUSR,AL	;SAVE EXTRA COPY FOR MULTI-DISK	DIRECTORIES
	MOV	CL,CURDSK
	CALL	CPM			;GET CURRENT DISK NR
	MOV	Byte Ptr OLDDSK,AL	;SAVE FOR RESET	IF NEEDED
	INC	AL
	MOV	Byte Ptr OUTFCB,AL	;SET DIRECTORY OUTPUT FILE DRIVE
	MOV	BX,FCB
	MOV	AL,M			;GET DRIVE NAME	FOR DIRECTORY SEARCH
	OR	AL,AL			;ANY SPECIFIED?
	JNZ	START2			;YES SKIP NEXT ROUTINE
	MOV	AL,Byte Ptr OLDDSK	;OTHERWISE, GET	DEFAULT	DISK
	INC	AL
START2:	MOV	M,AL			;PUT THE ABSOLUTE DRIVE	CODE IN	DIRECTORY FCB
;
; IF AT	LEAST ONE OPTION IS ALLOWED, SCAN THE COMMAND LINE FOR THE
; OPTION FIELD DELIMITER.  THE OPTION FIELD DELIMITER IS CONSIDERED
; VALID	ONLY IF	IT IS PRECEDED BY AT LEAST 1 SPACE (OTHERWISE, IT
; MAY BE PART OF THE DIRECTORY FILENAME).  ANY UNRECOGNIZED OPTIONS
; OR ILLEGAL USER NUMBERS WILL BE FLAGGED OR IGNORED (SEE REPERR).
; (WE SCAN THE COMMAND LINE BUFFER RATHER THAN THE 2ND DEFAULT FCB
; BECAUSE ALL 8	OPTIONS	PLUS A 2 DIGIT USER NUMBER WON'T FIT IN
; THE FCB NAME FIELD).
;
	MOV	BX,80H			;SET COMMAND LINE BUFFER POINTER
	MOV	CH,M			;GET LENGTH OF COMMAND LINE BUFFER
;
; SEARCH FOR THE COMMAND LINE DELIMITER.  IF NOT FOUND, ASSUME NO OPTIONS.
;
SCNDOL:	LAHF
	INC	BX
	SAHF
	DEC	CH
	JNS	L_1	
	JMP	CKREST			;EXIT IF COMMAND LINE BUFFER EMPTY
L_1:
	MOV	AL,M
	CMP	AL,'$'
	JNZ	SCNDOL
	DEC	BX			;'$' FOUND - MAKE SURE SPACE PRECEDES IT
	MOV	AL,M
	INC	BX
	CMP	AL,' '
	JNZ	SCNDOL			;NO SPACE - IGNORE "$" AND SEARCH AGAIN
;
; VALID	DELIMITER FOUND.  SCAN THE REST	OF THE BUFFER FOR OPTIONS.  ERRORS
; PAST THIS POINT WILL CAUSE AN	ABORT IF THE COMMAND LINE ERROR	OPTION IS
; ENABLED.  OTHERWISE, THE DUD OPTION WILL BE IGNORED AND SD WILL ATTEMPT
; TO CONTINUE STUMBLING	THROUGH	THE REST OF THE	FIELD.
;
	XCHG	BX,DX			;GET OPTION FIELD POINTER TO DE
SCNOPT:	LAHF				;BUMP TO NEXT OPTION FIELD CHARACTER
	INC	DX
	SAHF
	DEC	CH			;DOCK CHARACTERS LEFT IN OPTION	FIELD
	JNS	L_2	
	JMP	CKREST			;IF OPTION FIELD EXHAUSTED, EXIT
L_2:
SCNAGN:	MOV	SI,DX			;GET THE NEXT OPTION CHARACTER
	MOV	AL,[SI]
	CMP	AL,' '			;DO WE HAVE A SPACE?
	JZ	SCNOPT			;IGNORE	IT IF SO
	MOV	BX,(Offset OTBL)-1	;GET BASE OF OPTION LOOKUP TABLE
	MOV	CL,(Offset OEND)-(Offset OTBL)+1	;GET LENGTH OF OPTION LOOKUP TABLE
NOMACH:	INC	BX			;BUMP TO NEXT OPTION TABLE CHARACTER
	DEC	CL			;ARE WE	OUT OF THE TABLE?
	JZ	CK4USR			;IF SO, CHECK FOR USER OPTION
	CMP	AL,M			;COMPARE OUR CHARACTER WITH OPTION TABLE
	JNZ	NOMACH			;EXIT IF NO MATCH
	MOV	M,0			;OTHERWISE, ACTIVATE THE FLAG
	JMPS	SCNOPT			;..AND GO GET THE NEXT OPTION CHARACTER
;
; IF OPTION CHARACTER DOESN'T MATCH THE TABLE, SEE IF WE HAVE A USER
; OPTION.
;

	IF	UOPT
CK4USR:				;CHECK FOR USER	NUMBER OPTION
	CMP	AL,'U'
	JNZ	CLERR			;LAST OPTION, SO BAD DEAL IF THAT AIN'T IT
UAGN:	LAHF				;BUMP TO USER NUMBER DIGIT
	INC	DX
	SAHF
	DEC	CH
	JS	CLERR			;ERROR IF NOTHING LEFT
	MOV	SI,DX			;GET DECIMAL DIGIT
	MOV	AL,[SI]
	CMP	AL,' '			;IGNORE	LEADING	SPACES
	JZ	UAGN
	SUB	AL,30H			;SUBTRACT ASCII	BIAS
	JB	CLERR			;ERROR IF < 0
	CMP	AL,10
	JNB	CLERR			;ERROR IF > 9
	MOV	Byte Ptr NEWUSR,AL	;SAVE USER NUMBER AS IT	MAY BE ONLY 1 DIGIT
	MOV	Byte Ptr BASUSR,AL	;DUPLICATE IT IF MULTI-DISK MODE
	LAHF				;BUMP TO POSSIBLE 2ND DIGIT OF USER NUMBER
	INC	DX
	SAHF
	DEC	CH
	JS	CKREST			;IF NO MORE BUFFER, EXIT WITH COMPLETE USER #
	MOV	SI,DX			;ELSE, CHECK FOR ANOTHER DIGIT
	MOV	AL,[SI]
	SUB	AL,30H
	JB	SCNAGN			;IF NEXT CHAR NOT NUMERIC, ITS NOT PART	OF
	CMP	AL,10			;..USER	NUMBER SO GO CHECK FOR ANOTHER OPTION
	JNB	SCNAGN
	MOV	BL,AL			;SAVE UNITS DIGIT
	MOV	AL,Byte Ptr NEWUSR	;GET TENS DIGIT
	ADD	AL,AL			;MULTIPLY BY 10
	MOV	BH,AL
	ADD	AL,AL
	ADD	AL,AL
	ADD	AL,BH
	ADD	AL,BL			;COMBINE WITH UNITS DIGIT
	MOV	Byte Ptr NEWUSR,AL	;SAVE THE TOTAL	USER NUMBER
	MOV	Byte Ptr BASUSR,AL	;DUPLICATE IT IF MULTI-DISK MODE
	JMP	SCNOPT			;CONTINUE SCANNING
	ENDIF

;
; IF COMMAND LINE ERROR	OPTION ENABLED,	PLAYBACK THE COMMAND LINE UP
; TO THE CHARACTER THAT	WE GAGGED ON AND EXIT.	IF REPERR IS NOT ENABLED,
; THEN CONTINUE	AS IF NOTHING WERE AMISS TO AVOID ACKNOWLEDGING	THAT
; SOME OPTIONS ARE AVAILABLE.
;

	IF	REPERR
CLERR:
	XOR	AL,AL
	LAHF				;TAG END OF COMMAND LINE WITH TERMINATOR
	INC	DX
	SAHF
	MOV	SI,DX
	MOV	[SI],AL
	CALL	CRLF
	MOV	DX,(Offset ERRMS2)
	CALL	PRINT
	MOV	DX,(Offset ERRTAG)
	CALL	PRINT
	MOV	BX,81H			;PLAYBACK BAD COMMAND LINE TO ERROR POINT
CLELP:	MOV	AL,M
	OR	AL,AL
	JZ	CLEX
	CALL	SEND_CHAR
	INC	BX
	JMPS	CLELP
CLEX:	MOV	AL,'?'			;TAG LINE WITH A '?' FIELD
	CALL	SEND_CHAR
	CALL	CRLF			;SPACE DOWN 1 MORE LINE
	JMP	EXIT			;..AND RETURN TO CP/M
	ENDIF

;
; OPTIONS INPUT OR NOT SPECIFIED.  IF RESET OPTION SPECIFIED, DO IT NOW.
;
CKREST:
	MOV	AL,Byte Ptr ROPFLG	;IF RESET FLAG SET, RESET DISK SYSTEM BEFORE
	OR	AL,AL			;..STARTING TO UPDATE ALLOCATION VECTORS
	MOV	CL,RESET
	JNZ	L_3	
	CALL	CPM
L_3:
;
; VALIDATE DRIVE CODE AND USER AREA NUMBERS FROM THE DRIVE TABLE.
;
NOOPT:	MOV	DX,(Offset DREMSG)	;GET THE DRIVE/USER ERROR MESSAGE
	PUSH	DX
	MOV	AL,Byte Ptr .FCB	;GET DIRECTORY DRIVE CODE
	DEC	AL			;NORMALIZE TO RANGE OF 0-15
	CMP	AL,(Offset HIDRV)-(Offset LODRV)	;COMPARE WITH MAXIMUM DRIVES ON-LINE
	JNAE	L_4	
	JMP	ERXIT			;TAKE DRIVE ERROR EXIT IF OUT OF RANGE
L_4:
	MOV	BX,(Offset USRMSG)	;SWITCH	TO USER	# ERROR	MESSAGE
	MOV	BP,SP
	XCHG	BX,[BP]
	MOV	DL,AL			;USE DRIVE CODE	AS INDEX INTO TABLE
	MOV	DH,0
	MOV	BX,(Offset LODRV)	;POINT TO BASE OF DRIVE/USER TABLE
	ADD	BX,DX
	MOV	AL,M			;GET THE MAXIMUM USER #	FOR THIS DRIVE
	AND	AL,0FH			;MAKE SURE ITS IN RANGE	0 - 15
	MOV	Byte Ptr MAXUSR,AL	;SAVE IT FOR LATER
	MOV	BX,(Offset NEWUSR)	;POINT TO THE DIRECTORY	USER AREA
	CMP	AL,M			;COMPARE IT WITH THE MAXIMUM
	JNB	L_5	
	JMP	ERXIT			;TAKE ERROR EXIT IF USER NUMBER	ILLEGAL
L_5:
	POP	DX			;DESTROY ERROR MESSAGE POINTER
	MOV	BX,FCB+1		;POINT TO NAME
	MOV	AL,M			;ANY SPECIFIED?
	CMP	AL,' '
	JNZ	GOTFCB
;
; NO FCB - MAKE	FCB ALL	'?'
;
	MOV	CH,11			;FN+FT COUNT
QLOOP:	MOV	M,'?'			;STORE '?' IN FCB
	LAHF
	INC	BX
	SAHF
	DEC	CH
	JNZ	QLOOP
GOTFCB:	MOV	AL,'?'			;FORCE WILD EXTENT
	MOV	Byte Ptr .FCB+12,AL
	CALL	SETSRC			;SET DMA FOR BDOS MEDIA CHANGE CHECK
	MOV	BX,FCB			;POINT TO FCB DRIVE CODE FOR DIRECTORY
	MOV	DL,M			;GET THE DRIVE CODE OUT OF THE FCB
	DEC	DL			;NORMALIZE DRIVE CODE FOR SELECT
	MOV	CL,SELDSK		;SELECT THE DIRECTORY DRIVE TO RETRIEVE
	CALL	CPM			;..THE PROPER ALLOCATION VECTOR
	MOV	CL,curdpb		;get disk parameter block
	INT	224
	INC	BX			;point to block shift
	INC	BX
	MOV	AL,ES: M		;[AL]  =  block shift  =  log2(records/block)
	SUB	AL,3			;[AL] = log2(kbytes/block)
	MOV	Byte Ptr blkshf,AL
	INC	BX			;point to disk size
	mov	al,es: m		;get block mask, and save it
	mov	Byte Ptr blkmsk,al
	INC	BX
	INC	BX
	MOV	DL,ES: M
	INC	BX
	MOV	DH,ES: M
	MOV	Word Ptr dsksiz,DX	;dsksiz = blocks/dsk
;
;
; Now get amount used on disk
;
allctd:	MOV	CL,galloc		;get allocation vector address
	INT	224
;
	push	ds			;save the data segment at all cost...
	MOV	DX,Word Ptr dsksiz	;get blocks/disk
	mov	si,bx			;make indirect pointer
	test	dl,7			;big blocks or little blocks?
	jz	divlp1
	add	dx,8
divlp1:	mov	cl,3			;the great divide...
	shr	dx,cl
	mov	cx,dx
	mov	dl,0
	push	es			;trick to get to proper data segment
	pop	ds
cntit:	lods	ds: m	;nice way to get data to [AL] pointed by 0[BX] from [SI]
			;...with nifty increment of pointer at [SI]
	mov	bx,cx
	mov	cx,8			;scan thru 8 bits for 1's set
try1:	shr	al,1
	jnb	not1
	inc	dx			;got a live one, bump 'used' counter
not1:	loop	try1
	mov	cx,bx
	loop	cntit
	pop	ds			;restore to orginal data segment
;
	MOV	BX,Word Ptr dsksiz	;[BX] = blocks/disk-1, [DX] = blocks used
	inc	bx			;bump for actual disk size
	MOV	AL,BL			;do a sixteen bit subtract
	SUB	AL,DL
	MOV	BL,AL
	MOV	AL,BH
	SBB	AL,DH
	MOV	BH,AL			;[BX] = unused blocks
	MOV	AL,Byte Ptr blkshf	;now convert from blocks to kilobytes
	OR	AL,AL
shftit:	JZ	savit
	SHL	BX,1
	DEC	AL
	JMPS	shftit
;
savit:	MOV	Word Ptr freeby,BX	;[BX] = kbytes unused
;
;
; REENTER HERE ON SUBSEQUENT PASSES WHILE IN THE ALL-USERS MODE
;
SETTBL:	MOV	BX,Word Ptr dsksiz	;GET DIRECTORY MAXIMUM AGAIN
	LAHF				;DIRECTORY SIZE	IS dsksiz+1
	INC	BX
	SAHF
	SHL	BX,1			;DOUBLE	DIRECTORY SIZE
	MOV	DX,(Offset ORDER)	;TO GET	SIZE OF	ORDER TABLE
	LAHF				;ALLOCATE ORDER	TABLE
	ADD	BX,DX
	RCR	SI,1
	SAHF
	RCL	SI,1
	MOV	Word Ptr TBLOC,BX	;NAME TABLE BEGINS WHERE ORDER TABLE ENDS
	MOV	Word Ptr NEXTT,BX
	XCHG	BX,DX
;
;+++ MAY HAVE TO CHECK FOR AVAILABLE MEMORY HERE +++
;

	IF	UOPT
	MOV	AL,Byte Ptr NEWUSR	;GET USER AREA FOR DIRECTORY
	MOV	DL,AL
	MOV	CL,CURUSR		;GET THE USER FUNCTION
	CALL	CPM			;..AND SET NEW USER NUMBER
	ENDIF

;
; LOOK UP THE FCB IN THE DIRECTORY
;
SFIRST:	MOV	BX,0
	MOV	Word Ptr COUNT,BX	;INITIALIZE MATCH COUNTER
	MOV	Word Ptr TOTFIL,BX	;     "	    TOTAL FILE COUNTER
	MOV	Word Ptr TOTSIZ,BX	;     "	    TOTAL SIZE COUNTER
	CALL	SETSRC			;SET DMA FOR DIRECTORY SEARCH
	MOV	CL,SEARCH		;GET 'SEARCH FIRST' FUNCTION
	JMPS	LOOK			;..AND GO SEARCH FOR 1ST MATCH
;
; READ MORE DIRECTORY ENTRIES
;
MORDIR:	MOV	CL,NEXT			;SEARCH	NEXT
LOOK:	MOV	DX,FCB
	CALL	CPM			;READ DIRECTORY	ENTRY
	INC	AL			;CHECK FOR END (0FFH)
	JNZ	L_6	
	JMP	SPRINT			;IF NO MORE, SORT & PRINT WHAT WE HAVE
L_6:
;
; POINT	TO DIRECTORY ENTRY
;
SOME:	DEC	AL			;UNDO PREV 'INR A'
	AND	AL,3			;MAKE MODULUS 4
	ADD	AL,AL			;MULTIPLY...
	ADD	AL,AL			;..BY 32 BECAUSE
	ADD	AL,AL			;..EACH	DIRECTORY
	ADD	AL,AL			;..ENTRY IS 32
	ADD	AL,AL			;..BYTES LONG
	MOV	BX,BASE+81H		;POINT TO BUFFER
;
;SKIP TO FN/FT
;
	ADD	AL,BL			;POINT TO ENTRY
	ADD	AL,9			;POINT TO SYS BYTE
	MOV	BL,AL			;SAVE (CAN'T CARRY TO H)

	IF	SOPT
	MOV	AL,Byte Ptr SOPFLG	;DID USER REQUEST SYS FILES?
	OR	AL,AL
	JZ	SYSFOK
	ENDIF

	MOV	AL,M			;GET SYS BYTE
	OR	AL,AL			;CHECK BIT 7
	JS	MORDIR			;SKIP THAT FILE
SYSFOK:	MOV	AL,BL			;GO BACK NOW
	SUB	AL,10			;BACK TO USER NUMBER (ALLOC FLAG)
	MOV	BL,AL			;HL POINTS TO ENTRY NOW
	MOV	AL,Byte Ptr NEWUSR	;GET CURRENT USER
	CMP	AL,M
	JNZ	MORDIR			;IGNORE	IF DIFFERENT
	INC	BX
;
; MOVE ENTRY TO	TABLE
;
	XCHG	BX,DX			;ENTRY TO DE
	MOV	BX,Word Ptr NEXTT	;NEXT TABLE ENTRY TO HL
	MOV	CH,12			;ENTRY LENGTH (NAME, TYPE, EXTENT)
TMOVE:	MOV	SI,DX			;GET ENTRY CHAR
	MOV	AL,[SI]
	AND	AL,7FH			;REMOVE	ATTRIBUTES
	MOV	M,AL			;STORE IN TABLE
	LAHF
	INC	DX
	SAHF
	LAHF
	INC	BX
	SAHF
	DEC	CH			;MORE?
	JNZ	TMOVE
	LAHF
	INC	DX
	SAHF
	LAHF				;POINT TO SECTOR COUNT
	INC	DX
	SAHF
	MOV	SI,DX			;GET IT
	MOV	AL,[SI]
	MOV	M,AL			;STORE IN TABLE
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr NEXTT,BX	;SAVE UPDATED TABLE ADDR
	XCHG	BX,DX
	MOV	BX,Word Ptr COUNT	;BUMP THE # OF MATCHES MADE
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr COUNT,BX
	MOV	BX,13			;SIZE OF NEXT ENTRY
	LAHF
	ADD	BX,DX
	RCR	SI,1
	SAHF
	RCL	SI,1
	XCHG	BX,DX			;FUTURE	NEXTT IS IN DE
	JMP	MORDIR			;ATTEMPT TO GET NEXT DIRECTORY ENTRY
;
; SORT AND PRINT
;
SPRINT:
	CALL	SETFOP			;RETURN TO FILE OUTPUT DMA & USER #
	MOV	BX,Word Ptr COUNT	;GET FILE NAME COUNT
	MOV	AL,BL
	OR	AL,BH			;ANY FOUND?
	JNZ	L_7	
	JMP	PRTOTL			;EXIT IF NO FILES FOUND
L_7:
	PUSH	BX			;SAVE FILE COUNT
	MOV	Byte Ptr SUPSPC,AL	;ENABLE	LEADING	ZERO SUPPRESSION
;
; INITIALIZE THE ORDER TABLE
;
	MOV	BX,Word Ptr TBLOC	;GET START OF NAME TABLE
	XCHG	BX,DX			;INTO DE
	MOV	BX,(Offset ORDER)	;POINT TO ORDER	TABLE
	MOV	CX,13			;ENTRY LENGTH
BLDORD:	MOV	M,DL			;SAVE LOW ORDER	ADDRESS
	INC	BX
	MOV	M,DH			;SAVE HIGH ORDER ADDRESS
	INC	BX
	XCHG	BX,DX			;TABLE ADDR TO HL
	ADD	BX,CX			;POINT TO NEXT ENTRY
	XCHG	BX,DX
	MOV	BP,SP			;SAVE TBL ADDR,	FETCH LOOP COUNTER
	XCHG	BX,[BP]
	DEC	BX			;COUNT DOWN LOOP
	MOV	AL,BL
	OR	AL,BH			;MORE?
	MOV	BP,SP			;(RESTORE TBL ADDR, SAVE COUNTER)
	XCHG	BX,[BP]
	JNZ	BLDORD			;YES, GO DO ANOTHER ONE
	POP	BX			;CLEAN LOOP COUNTER OFF	STACK
	MOV	BX,Word Ptr COUNT	;GET COUNT
	MOV	Word Ptr SCOUNT,BX	;SAVE AS # TO SORT
	DEC	BX			;ONLY 1	ENTRY?
	MOV	AL,BL
	OR	AL,BH
	JNZ	L_8	
	JMP	DONE			;YES, SO SKIP SORT
L_8:
;
; THIS SORT ROUTINE IS ADAPTED FROM SOFTWARE TOOLS
; BY KERNIGAN AND PLAUGHER.
;
SORT:	MOV	BX,Word Ptr SCOUNT	;NUMBER	OF ENTRIES
SORT0:	OR	AL,AL			;CLEAR CARRY
	MOV	AL,BH			;GAP=GAP/2
	RCR	AL,1
	MOV	BH,AL
	MOV	AL,BL
	RCR	AL,1
	MOV	BL,AL
	OR	AL,BH			;IS IT ZERO?
	JNZ	L_9	
	JMP	DONE			;THEN NONE LEFT
L_9:
	MOV	AL,BL			;MAKE GAP ODD
	OR	AL,01
	MOV	BL,AL
	MOV	Word Ptr GAP,BX
	INC	BX			;I=GAP+1
SORT2:	MOV	Word Ptr I,BX
	XCHG	BX,DX
	MOV	BX,Word Ptr GAP
	MOV	AL,DL			;J=I-GAP
	SUB	AL,BL
	MOV	BL,AL
	MOV	AL,DH
	SBB	AL,BH
	MOV	BH,AL
SORT3:	MOV	Word Ptr J,BX
	XCHG	BX,DX
	MOV	BX,Word Ptr GAP		;JGAP=J+GAP
	LAHF
	ADD	BX,DX
	SAHF
	MOV	Word Ptr JGAP,BX
	MOV	AL,12			;COMPARE 12 CHARS
	CALL	COMPARE			;COMPARE (J) AND (JG)
	JNS	SORT5			;IF A(J)<=A(JG)
	MOV	BX,Word Ptr J
	XCHG	BX,DX
	MOV	BX,Word Ptr JGAP
	CALL	SWAP			;EXCHANGE A(J) AND A(JG)
	MOV	BX,Word Ptr J		;J=J-GAP
	XCHG	BX,DX
	MOV	BX,Word Ptr GAP
	MOV	AL,DL
	SUB	AL,BL
	MOV	BL,AL
	MOV	AL,DH
	SBB	AL,BH
	MOV	BH,AL
	JS	SORT5			;IF J>0	GOTO SORT3
	OR	AL,BL			;CHECK FOR ZERO
	JZ	SORT5
	JMPS	SORT3
SORT5:	MOV	BX,Word Ptr SCOUNT	;FOR LATER
	XCHG	BX,DX
	MOV	BX,Word Ptr I		;I=I+1
	INC	BX
	MOV	AL,DL			;IF I<=N GOTO SORT2
	SUB	AL,BL
	MOV	AL,DH
	SBB	AL,BH
	JS	L_10	
	JMP	SORT2
L_10:
	MOV	BX,Word Ptr GAP
	JMP	SORT0
;
; SORT IS ALL DONE - PRINT ENTRIES
;
DONE:	MOV	AL,Byte Ptr FOPFLG
	OR	AL,AL
	JZ	L_11	
	JMP	NOOUT			;IF FILE OUTPUT, FALL THROUGH WITH A=0
L_11:
;
; IF ALL USER OPTION ENABLED, AND WE'RE NOT ON THE FIRST PASS, THEN THE
; OUTPUT FILE IS ALREADY OPEN AND POSITIONED, SO WE CAN SKIP THE OPEN.
;
	MOV	BX,(Offset OPNFLG)	;POINT TO OUTPUT FILE OPEN FLAG
	CMP	AL,M			;A=0, SET Z IF OPNFLG=0 ALSO
	JZ	L_12	
	JMP	NOOUT			;IF OPNFLG NOT ZERO, SKIP OPEN
L_12:
	DEC	M			;ELSE, MAKE OPNFLG NOT ZERO AND OPEN
;
; FIRST PASS ON FILE APPEND - PREPARE XDIR.DIR TO RECEIVE NEW OR APPENDED
; OUTPUT.
;
	MOV	DX,(Offset OUTFCB)	;DOES OUTPUT FILE ALREADY EXIST?
	MOV	CL,SEARCH
	CALL	CPM
	INC	AL
	JNZ	OPENIT			;IF IT DOES, THEN OPEN IT FOR PROCESSING
	MOV	CL,MAKE			;OTHERWISE, CREATE THE OUTPUT FILE
	CALL	CPM
	INC	AL
	JZ	L_13	
	JMP	NOOUT			;CONTINUE IF OPEN SUCCESSFUL
L_13:
;
; IF MAKE OR OPEN FAILS, DECLARE ERROR
;
OPNERR:	CALL	ERXIT
	DB	'OPE','N' OR 80H
;
WRTERR:	CALL	ERXIT
	DB	'WRIT','E' OR 80H
;
; OUTPUT FILE ALREADY EXISTS - OPEN IT AND POSITION TO THE LAST
; RECORD OF THE	LAST EXTENT.
;
OPENIT:	MOV	CL,OPEN			;OPEN 1ST EXTENT OF OUTPUT FILE
	CALL	CPM
	INC	AL
	JZ	OPNERR			;BAD DEAL IF 1ST WON'T OPEN
OPNMOR:	MOV	AL,Byte Ptr OUTFCB+15
	CMP	AL,128
	JB	LSTEXT			;IF RC<128, THIS IS LAST EXTENT
	MOV	BX,(Offset OUTFCB)+12
	INC	M			;ELSE, BUMP TO NEXT EXTENT
	MOV	CL,OPEN			;..AND TRY TO OPEN IT
	CALL	CPM
	INC	AL
	JNZ	OPNMOR			;CONTINUE OPENING EXTENTS TIL NO MORE
	DEC	M			;THEN, REOPEN PRECEDING	EXTENT
	MOV	CL,OPEN
	CALL	CPM
	MOV	AL,Byte Ptr OUTFCB+15	;GET RC	FOR THE	LAST EXTENT
;
; AT THIS POINT, OUTFCB IS OPENED TO THE LAST EXTENT OF THE FILE,
; SO READ IN THE LAST RECORD IN THE LAST EXTENT.
;
LSTEXT:	OR	AL,AL			;IS THIS EXTENT	EMPTY?
	JZ	NOOUT			;IF SO, THEN WE'RE STARTING A CLEAN SLATE
	DEC	AL			;NORMALIZE RECORD COUNT
	MOV	Byte Ptr OUTFCB+32,AL	;SET RECORD NUMBER TO READ
	MOV	CL,READ			;..AND READ LAST RECORD	OF FILE
	CALL	CPM
	OR	AL,AL			;WAS READ SUCCESSFUL?
	JZ	RDOK			;IF SO,	PROCEED	TO SCAN	FOR EOF	MARK
APERR:	CALL	ERXIT
	DB	'APPEN','D' OR 80H
;
; WE NOW HAVE THE LAST RECORD IN THE FILE IN OUR BUFFER.
; SCAN THE LAST	RECORD FOR THE EOF MARK, INDICATING WHERE
; WE CAN START ADDING DATA.
;
RDOK:	MOV	BX,(Offset OUTBUF)	;POINT TO START OF OUTPUT BUFFER
	MOV	CH,128			;GET LENGTH OF OUTPUT BUFFER
SCAN:	MOV	AL,M
	CMP	AL,'Z'-40H		;HAVE WE FOUND END OF FILE?
	JZ	RESCR			;IF SO, SAVE POINTERS AND RESET CR
	LAHF
	INC	BX
	SAHF
	DEC	CH
	JNZ	SCAN			;OTHERWISE, KEEP LOOKING TIL END OF BUFFER
;
; IF WE FIND AN EXPLICIT EOF MARK IN THE LAST BUFFER (OR AN IMPLIED EOF
; IF THE LAST RECORD IS FULL), MOVE THE FCB RECORD AND EXTENT POINTERS
; BACK TO CORRECT FOR THE READ OPERATION SO THAT OUR FIRST WRITE OPERATION
; WILL EFFECTIVELY REPLACE THE LAST RECORD OF THE XDIR.DIR FILE.
;
RESCR:	PUSH	BX			;SAVE EOF BUFFER POINTER
	PUSH	CX			;SAVE EOF BUFFER REMAINING
	MOV	BX,(Offset OUTFCB)+32	;GET CURRENT RECORD AGAIN
	DEC	M			;DOCK IT
	JNS	SAMEXT			;IF CR >=0, WE'RE STILL IN SAME EXTENT
	MOV	BX,(Offset OUTFCB)+12	;ELSE, MOVE TO PREVIOUS	EXTENT
	DEC	M
	MOV	CL,OPEN			;THEN, REOPEN THE PREVIOUS EXTENT
	CALL	CPM
	INC	AL
	JZ	APERR			;APPEND POSITION ERROR IF WE CAN'T REOPEN
	MOV	AL,Byte Ptr OUTFCB+15	;ELSE, POSITION TO LAST RECORD OF EXTENT
	DEC	AL
	MOV	Byte Ptr OUTFCB+32,AL
SAMEXT:	POP	AX			;RECALL WHERE EOF IS IN BUFFER
	XCHG	AL,AH
	SAHF
	MOV	Byte Ptr BUFCNT,AL	;..AND SET BUFFER COUNTER
	POP	BX			;RECALL NEXT BUFFER POINTER
	MOV	Word Ptr BUFPNT,BX	;.. AND SET POINTER FOR FIRST ADDITION
NOOUT:	MOV	BX,(Offset ORDER)	;INITIALIZE ORDER TABLE	POINTER
	MOV	Word Ptr NEXTT,BX
	JMP	NEWLIN			;START NEW LINE	AND OUTPUT THE FILES
;
; OUTPUT THE DIRECTORY FILES WE'VE MATCHED.
;
ENTRY:	MOV	BX,Word Ptr COUNT
	DEC	BX			;DOCK FILE COUNT
	MOV	Word Ptr COUNT,BX
	MOV	AL,BH			;IS THIS THE LAST FILE?
	OR	AL,BL
	JZ	OKPRNT			;IF COUNT=0, LAST FILE SO SKIP COMPARE
;
; COMPARE EACH ENTRY TO	MAKE SURE THAT IT ISN'T PART OF A MULTIPLE
; EXTENT FILE.	GO ONLY	WHEN WE	HAVE THE LAST EXTENT OF	THE FILE.
;
	PUSH	CX			;SAVE NPL
	CALL	CKABRT			;CHECK FOR ABORT CODE FROM KEYBOARD
	MOV	BX,Word Ptr NEXTT
	MOV	AL,11
	CALL	COMPR			;DOES THIS ENTRY MATCH NEXT ONE?
	POP	CX			;RECALL	NPL
	JNZ	OKPRNT			;NO, PRINT IT
	INC	BX
	INC	BX			;SKIP SINCE HIGHEST EXTENT COMES LAST IN LIST
	MOV	Word Ptr NEXTT,BX
	JMPS	ENTRY			;LOOP BACK FOR NEXT LOWEST EXTENT
;
; VALID	ENTRY OBTAINED - SPIT IT OUT.
;
OKPRNT:	MOV	BX,Word Ptr NEXTT	;GET ORDER TABLE POINTER
	MOV	DL,M			;GET LOW ORDER ADDRESS
	LAHF
	INC	BX
	SAHF
	MOV	DH,M			;GET HIGH ORDER	ADDRESS
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr NEXTT,BX	;SAVE UPDATED TABLE POINTER
	XCHG	BX,DX			;TABLE ENTRY TO	HL
	MOV	CH,8			;FILE NAME LENGTH
	CALL	TYPEIT			;TYPE FILENAME
	MOV	AL,'.'			;PERIOD	AFTER FN
	CALL	SEND_CHAR
	MOV	CH,3			;DISPLAY 3 CHARACTERS OF FILETYPE
	CALL	TYPEIT
;
; COMPUTE THE SIZE OF THE FILE AND UPDATE OUR SUMMARY DATUM.
;
	MOV	DL,M			;GET EXTENT #
	MOV	DH,0
	INC	BX
	MOV	AL,M			;GET SECTOR COUNT OF LAST EXTENT
	XCHG	BX,DX
	SHL	BX,1			;# OF EXTENTS TIMES 16K
	SHL	BX,1
	SHL	BX,1
	SHL	BX,1
	XCHG	BX,DX			;SAVE IN DE
	MOV	BX,(Offset BLKMSK)
	ADD	AL,M			;ROUND LAST EXTENT TO BLOCK SIZE
	ROR	AL,1
	ROR	AL,1			;CONVERT FROM SECTORS TO K
	ROR	AL,1
	AND	AL,1FH
	MOV	BL,AL			;ADD TO	TOTAL K
	MOV	BH,0
	ADD	BX,DX
	MOV	AL,Byte Ptr BLKMSK	;GET SECTORS/BLK-1
	ROR	AL,1
	ROR	AL,1			;CONVERT TO K/BLK
	ROR	AL,1
	AND	AL,1FH
	NOT	AL			;USE TO	FINISH ROUNDING
	AND	AL,BL
	MOV	BL,AL
	XCHG	BX,DX			;SAVE FILE SIZE	IN DE
	MOV	BX,Word Ptr TOTSIZ
	LAHF				;ADD TO	TOTAL USED
	ADD	BX,DX
	RCR	SI,1
	SAHF
	RCL	SI,1
	MOV	Word Ptr TOTSIZ,BX
	MOV	BX,Word Ptr TOTFIL	;INCREMENT FILE	COUNT
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr TOTFIL,BX
	XCHG	BX,DX			;GET BACK FILE SIZE
;
; IF REPORT SIZE ENABLED, OUTPUT THE SIZE OF THE INDIVIDUAL FILE.
;IF FILE SIZE REPORT WANTED
;
	CALL	DECPRT			;..GO PRINT IT
	MOV	AL,'k'			;..AND FOLLOW WITH K SIZE
	CALL	SEND_CHAR
;
; ONE FILE OUTPUT - TEST TO SEE	IF WE HAVE TO OUTPUT ANOTHER ONE.
;
	MOV	BX,Word Ptr COUNT	;GET CURRENT FILE COUNTER AND TEST IT
	MOV	AL,BH
	OR	AL,BL
	JNZ	L_14	
	JMP	PRTOTL			;IF NO MORE FILES, EXIT	TO SUMMARY OUTPUT
L_14:
;
; AT LEAST ONE MORE FILE TO OUTPUT - CAN WE PUT	IT ON THE CURRENT LINE?
;
	DEC	CL
	LAHF
	XCHG	AL,AH
	PUSH	AX
	JZ	L_15	
	CALL	FENCE			;IF ROOM LEFT, OUTPUT THE FENCE	CHARACTER
L_15:
	POP	AX
	XCHG	AL,AH
	SAHF
	JZ	L_16	
	JMP	ENTRY			;.. AND	GO OUTPUT ANOTHER FILE
L_16:
;
; CURRENT LINE FULL, START A NEW ONE.
;
NEWLIN:	MOV	CL,NPL			;RESET NAMES PER LINE COUNTER
	CALL	CRLF			;SPACE DOWN TO NEXT LINE
	MOV	AL,Byte Ptr .FCB	;.. PRECEDE NEW	LINE WITH DRIVE	NAME
	ADD	AL,'A'-1
	CALL	SEND_CHAR
	MOV	AL,':'			;TAG DRIVE LETTER WITH A COLON AND SPACE
	CALL	SEND_CHAR
	JMP	ENTRY			;GO BACK AND OUTPUT ANOTHER FILE
;
;IF REPORTING USER NUMBERS
;
; PRINT	HL IN DECIMAL WITH LEADING ZERO	SUPPRESSION
;
DECPRT:	SUB	AL,AL			;CLEAR LEADING ZERO FLAG
	MOV	Byte Ptr LZFLG,AL
	MOV	DX,-1000		;PRINT 1000'S DIGIT
	CALL	DIGIT
	MOV	DX,-100			;ETC.
	CALL	DIGIT
	MOV	DX,-10
	CALL	DIGIT
	MOV	AL,'0'			;GET 1'S DIGIT
	ADD	AL,BL
	JMP	SEND_CHAR
;
DIGIT:	MOV	CH,'0'			;START OFF WITH	ASCII 0
DIGLP:	PUSH	BX			;SAVE CURRENT REMAINDER
	ADD	BX,DX			;SUBTRACT
	JNB	DIGEX			;QUIT ON OVERFLOW
	POP	AX			;THROW AWAY REMAINDER
	INC	CH			;BUMP DIGIT
	JMPS	DIGLP			;LOOP BACK
DIGEX:	POP	BX			;RESTORE POINTER
	MOV	AL,CH
	CMP	AL,'0'			;ZERO DIGIT?
	JNZ	DIGNZ			;NO, TYPE IT
	MOV	AL,Byte Ptr LZFLG	;LEADING ZERO?
	OR	AL,AL
	MOV	AL,'0'
	JZ	L_17	
	JMP	SEND_CHAR			;PRINT DIGIT
L_17:
	MOV	AL,Byte Ptr SUPSPC	;GET SPACE SUPPRESSION FLAG
	OR	AL,AL			;SEE IF	PRINTING FILE TOTALS
	JNZ	L_18	
	RET				;YES, DON'T GIVE LEADING SPACES
L_18:
	JMP	SPACE			;LEADING ZERO...PRINT SPACE
DIGNZ:	MOV	Byte Ptr LZFLG,AL	;SET LEADING ZERO FLAG SO NEXT ZERO PRINTS
	JMP	SEND_CHAR			;AND PRINT DIGIT
;
; SHOW TOTAL SPACE AND FILES USED
;
PRTOTL:	XOR	AL,AL			;GET A ZERO TO...
	MOV	Byte Ptr SUPSPC,AL	;SUPPRESS LEADING SPACES IN TOTALS
	MOV	BX,Word Ptr TOTFIL	;HOW MANY FILES	DID WE MATCH?
	MOV	AL,BH
	OR	AL,BL
	JZ	NXTUSR			;SKIP THE SUMMARY IF WE	DIDN'T FIND ANY
	PUSH	BX			;SAVE TOTFIL
	MOV	Byte Ptr FNDFLG,AL	;SET FILE FOUND FLAG
	MOV	DX,(Offset TOTMS1)	;PRINT [CR,LF,LF]"DRIVE	"
	CALL	PRINT
	MOV	AL,Byte Ptr .FCB
	ADD	AL,'A'-1
	CALL	SEND_CHAR			;OUTPUT	THE DRIVE CODE
	MOV	DX,(Offset TOTMS2)	;PRINT ", USER "
	CALL	PRINT
	CALL	TYPUSR			;OUTPUT	THE USER NUMBER
	MOV	DX,(Offset TOTMS3)	;PRINT " CONTAINS "
	CALL	PRINT
	MOV	BX,Word Ptr TOTSIZ	;PRINT TOTAL K USED BY FILES MATCHED
	CALL	DECPRT
	MOV	DX,(Offset TOTMS4)	;PRINT "K IN "
	CALL	PRINT
	POP	BX			;RECALL	TOTFIL
	CALL	DECPRT			;PRINT NUMBER OF FILES MATCHED
	MOV	DX,(Offset TOTMS5)	;PRINT " FILES WITH "
	CALL	PRINT
	CALL	PRTFRE			;OUTPUT	FREE SPACE REMAINING & " FREE."
;
; DIRECTORY FOR	ONE USER AREA COMPLETED.  IF ALL USERS OPTION IS
; SELECTED, THEN GO DO ANOTHER DIRECTORY ON THE	NEXT USER NUMBER
; UNTIL	WE EXCEED THE MAXIMUM USER # FOR THE SELECTED DRIVE.
;

	IF	AOPT
NXTUSR:				;IF ALL	USERS OPTION ENABLED
	MOV	AL,Byte Ptr AOPFLG	;IF NOT	ALL USERS MODE - SKIP NEXT
	OR	AL,AL
	JNZ	GOCLZ
	CALL	CKABRT			;CHECK FOR USER	ABORT FIRST
	MOV	AL,Byte Ptr MAXUSR	;NO ABORT - GET	MAXIMUM	USER NUMBER
	MOV	BX,(Offset NEWUSR)	;BUMP DIRECTORY	USER NUMBER
	INC	M
	CMP	AL,M			;DOES NEXT USER	# EXCEED MAXIMUM?
	JNAE	L_19	
	JMP	SETTBL			;CONTINUE IF MORE USER AREAS TO	GO
L_19:
	ENDIF

;
;IF MULTI-DISK OPTION ENABLED

	IF	AOPT AND DOPT
	MOV	AL,Byte Ptr BASUSR	;RESET BASE USER NUMBER	FOR THE
	MOV	M,AL			;..NEXT	DIRECTORY SEARCH
	ENDIF

;
; WE'VE FINISHED ALL OF OUR OUTPUTTING.  FLUSH THE REMAINDER OF THE
; OUTPUT BUFFER AND CLOSE THE FILE BEFORE GOING TO EXIT ROUTINE.
;

	IF	FOPT
GOCLZ:
	MOV	BX,(Offset OPNFLG)	;GET FILE OPEN STATUS THEN RESET FLAG TO
	MOV	AL,M			;..FORCE REOPEN ON NEXT PASS
	MOV	M,0
	OR	AL,AL
	JZ	NXTDSK			;SKIP CLOSING XDIR.DIR IF IT WASN'T OPENED
	MOV	BX,(Offset BUFCNT)
	MOV	AL,M			;RETRIEVE # OF UNFLUSHED CHARACTERS IN BUFFER
	MOV	M,128			;FORCE BUFCNT TO EMPTY STATUS FOR NEXT DRIVE
	OR	AL,AL			;IF BUFCNT=128, BUFFER EMPTY SO SET SIGN BIT
	JS	CLOZE			;CLOSE XDIR.DIR IF BUFFER IS EMPTY
	JZ	FLUSH			;WRITE LAST RECORD TO XDIR.DIR IF BUFFER FULL
	MOV	BX,Word Ptr BUFPNT	;OTHERWISE, PAD	UNUSED BUFFER WITH CTRL-ZS
PUTAGN:	MOV	M,'Z'-40H
	LAHF
	INC	BX
	SAHF
	DEC	AL
	JNZ	PUTAGN			;CONTINUE PADDING TIL BUFFER FILLED OUT
FLUSH:	MOV	DX,(Offset OUTFCB)	;FLUSH THE LAST	OUTPUT BUFFER
	MOV	CL,WRITE
	CALL	CPM
	OR	AL,AL
	JZ	L_20	
	JMP	WRTERR
L_20:
CLOZE:	MOV	DX,(Offset OUTFCB)	;CLOSE THE OUTPUT FILE
	MOV	CL,CLOSE
	CALL	CPM
	ENDIF

;
; DIRECTORY FOR	ALL USER AREAS COMPLETED.  IF THE MULTI-DISK OPTION
; IS ENABLED AND SELECTED, RESET TO THE	BASE USER AREA AND REPEAT
; THE DIRECTORY	FOR NEXT DRIVE ON-LINE UNTIL WE	EITHER EXCEED THE
; DRIVES IN OUR	LODRV-HIDRV TABLE, OR THE BDOS SHUTS US	DOWN WITH
; A SELECT OR BAD SECTOR ERROR,	WHICH WILL BE INTERCEPTED BACK TO
; THE EXIT MODULE.
;
NXTDSK:	MOV	BX,(Offset FNDFLG)	;GET FILE FOUND FLAG
	MOV	AL,M
	MOV	M,0			;CLEAR FILE FOUND FLAG FOR NEXT DRIVE
	OR	AL,AL
	JNZ	NDSK			;CONTINUE IF AT LEAST 1 FILE FOUND
;
;IF FILE OUTPUT ENABLED, DISABLE TEMPORARILY
;

	IF	FOPT
	MOV	BX,(Offset FOPFLG)
	DEC	M
	PUSH	BX
	ENDIF

	MOV	AL,Byte Ptr .FCB	;STASH ASCII DIRECTORY DRIVE IN NO FILE MSG
	ADD	AL,'A'-1
	MOV	Byte Ptr NOFMS2,AL
	MOV	DX,(Offset NOFMS1)	;PRINT "NO FILE ON ? - "
	CALL	PRINT
	CALL	PRTFRE			;TAG WITH FREE MESSAGE
;
;RESTORE ORIGINAL FILE OUTPUT MODE
;

	IF	FOPT
	POP	BX
	INC	M
	ENDIF

	IF	DOPT
NDSK:					;IF MULTI-DISK OPTION ENABLED
	MOV	AL,Byte Ptr DOPFLG	;IF MULTI-DISK NOT SELECTED - SKIP NEXT
	OR	AL,AL
	JZ	L_21	
	JMP	EXIT
L_21:
	CALL	CKABRT			;CHECK FOR USER	ABORT FIRST
	MOV	AL,(Offset HIDRV)-(Offset LODRV)	;GET MAXIMUM DRIVE CODE	TO SEARCH
	MOV	BX,FCB			;BUMP DIRECTORY	FCB DRIVE CODE
	INC	M
	CMP	AL,M			;DOES NEXT DISK	EXCEED MAXIMUM?
	JNAE	L_22	
	JMP	NOOPT			;SEARCH	NEXT DISK IF NOT
L_22:
	ENDIF

	JMP	EXIT			;ALL DONE - EXIT TO CCP
;
; PRINT	THE USER NUMBER	OF THE DIRECTORY IN DECIMAL
;
TYPUSR:
	MOV	AL,Byte Ptr NEWUSR
	CMP	AL,10			;IF USER NO. > 9 PRINT LEADING 1
	JB	DUX
	MOV	AL,'1'
	CALL	SEND_CHAR
	MOV	AL,Byte Ptr NEWUSR	;PRINT LOW DIGIT OF USER NO.
	SUB	AL,10
DUX:	ADD	AL,'0'
	JMPS	SEND_CHAR
;
; FORCE	NEW LINE ON VIDEO AND CHECK FOR	PAGE PAUSE
;
CRLF:	MOV	AL,0DH			;SEND CR
	CALL	SEND_CHAR
	MOV	AL,0AH			;SEND LF
	JMPS	SEND_CHAR			;EXIT TO CALLER	FROM TYPE
;
; SEPARATE THE DIRECTORY OUTPUT	ON A LINE WITH A SPACE,	THE DELIMITER,
; FOLLOWED BY ANOTHER SPACE.
;
FENCE:	CALL	SPACE
	MOV	AL,DELIM		;FENCE CHARACTER
FPAD:	CALL	SEND_CHAR			;PRINT IT, FALL	INTO SPACE
SPACE:	MOV	AL,' '			;FALL THROUGH TO TYPE
;
; OUTPUT CHARACTER IN A TO CONSOLE, AND OPTIONALLY TO PRINTER AND/OR
; THE OUTPUT FILE.
;
SEND_CHAR:
	PUSH	CX
	PUSH	DX
	PUSH	BX

	IF	FOPT OR POPT OR PGPAWZ
	LAHF				;SAVE THE CHARACTER TO OUTPUT
	XCHG	AL,AH
	PUSH	AX
	XCHG	AL,AH
	ENDIF

	CALL	TYPE1			;SEND IT TO CONSOLE

	IF	FOPT OR POPT OR PGPAWZ
	POP	AX			;RESTORE THE OUTPUT CHARACTER
	XCHG	AL,AH
	AND	AL,7FH			;STRIP PARITY BIT ON CHARACTER
	ENDIF

;
; TEST FILE OUTPUT MODE AND SKIP TO PAGE PAUSE TEST IF NOT ACTIVE.
;

	IF	FOPT
	MOV	CH,AL			;SAVE STRIPPED CHARACTER TO B
	MOV	AL,Byte Ptr FOPFLG	;IS FILE OUTPUT	ACTIVE?
	OR	AL,AL
	JNZ	NOWRIT			;GO CHECK FOR PAGE PAUSE IF NOT
;
; FILE OUTPUT MODE ACTIVE - MAKE SURE WE HAVE ROOM IN BUFFER TO ADD
; NEXT CHARACTER.  IF BUFFER FULL, WRITE OUT CURRENT RECORD FIRST
; AND THEN START A NEW RECORD WITH CURRENT CHARACTER.
;
	MOV	BX,Word Ptr BUFPNT	;GET CURRENT BUFFER POINTER
	MOV	AL,Byte Ptr BUFCNT	;GET BUFFER CAPACITY REMAINING
	OR	AL,AL
	JNZ	PUTBUF			;CONTINUE IF BUFFER NOT FULL
	MOV	DX,(Offset OUTFCB)	;OTHERWISE, WRITE THE CURRENT BUFFER OUT
	MOV	CL,WRITE
	CALL	CPM			;(NOTE CALL MUST SAVE CHARACTER	IN B)
	OR	AL,AL
	JZ	L_23	
	JMP	WRTERR			;TAKE WRITE ERROR EXIT IF DISK FULL OR R/O
L_23:
	MOV	BX,(Offset OUTBUF)	;RESET BUFFER POINTER
	MOV	AL,128			;RESET BUFFER CAPACITY
PUTBUF:	MOV	M,CH			;SHOVE CHARACTER TO NEXT BUFFER POSITION
	INC	BX			;BUMP BUFFER POINTER
	MOV	Word Ptr BUFPNT,BX	;.. AND SAVE IT
	DEC	AL			;DOCK COUNT OF CHARACTERS LEFT IN BUFFER
	MOV	Byte Ptr BUFCNT,AL	;..AND SAVE IT
NOWRIT:	MOV	AL,CH			;RECALL	STRIPPED CHARACTER
	ENDIF

;
;IF PRINTER OPTION
;

	IF	POPT
	MOV	DL,AL			;SETUP LIST OUTPUT CALL
	MOV	CL,5
	MOV	AL,Byte Ptr POPFLG	;TEST PRINTER FLAG
	OR	AL,AL
	JNZ	L_24	
	CALL	CPM			;PRINT CHARACTER IF FLAG TRUE
L_24:
	MOV	AL,DL			;RECALL	CHARACTER
	ENDIF


	IF	PGPAWZ
	CMP	AL,0AH			;DO WE HAVE A LF?
	JNZ	TYPRET			;EXIT IF NOT
	ENDIF

	IF	NOPT AND PGPAWZ
	MOV	AL,Byte Ptr NOPFLG	;IS THE	PAGE PAUSE FUNCTION DISABLED?
	OR	AL,AL
	JZ	TYPRET			;EXIT IF SO
	ENDIF

	IF	PGPAWZ
	MOV	AL,Byte Ptr LINCNT	;GET LINE COUNT
	INC	AL			;BUMP IT
	CMP	AL,LPS			;ARE WE	AT THE END OF THE SCREEN?
	JB	NOTEOS			;SKIP IF NOT
	MOV	DX,(Offset EOSMSG)	;ELSE, DISPLAY PAUSE MESSAGE
	MOV	CL,9			;..WITHOUT CHECKING FOR	LFS
	INT	224
	CALL	CINPUT			;WAIT FOR CHARACTER
	CMP	AL,'C'-40H
	JNZ	L_25	
	JMP	EXIT			;ABORT ON CTRL-C
L_25:
	XOR	AL,AL			;RESET LINE COUNT
NOTEOS:	MOV	Byte Ptr LINCNT,AL	;SAVE NEW LINE COUNT
	ENDIF

TYPRET:	POP	BX			;EXIT FROM TYPE
	POP	DX
	POP	CX
	RET
;
; OUTPUT CHARACTER
;
TYPE1:	MOV	DL,AL			;GET CHARACTER INTO BDOS ENTRY REGISTER
	MOV	CL,WRCHR
	INT	224			;CALL CONOUT VIA THE BDOS
	RET
;
; PRINT	A STRING AT HL OF LENGTH B
;
TYPEIT:	MOV	AL,M
	CALL	SEND_CHAR
	LAHF
	INC	BX
	SAHF
	DEC	CH
	JNZ	TYPEIT
	RET
;
; PRINT	STRING TERMINATED WITH LAST BYTE HIGH ON CONSOLE.
;
PRINT:	MOV	SI,DX
	MOV	AL,[SI]
	LAHF
	XCHG	AL,AH
	PUSH	AX
	XCHG	AL,AH
	AND	AL,7FH
	CALL	SEND_CHAR
	POP	AX
	XCHG	AL,AH
	OR	AL,AL
	JNS	L_26
	RET
L_26:
	LAHF
	INC	DX
	SAHF
	JMPS	PRINT
;
; FETCH	CHARACTER FROM CONSOLE (WITHOUT	ECHO)
;
CINPUT:	MOV	Byte Ptr FUNC,3		;bios console input function
	MOV	Word Ptr BIOS_DESC,CX	;pass disk number to bios descriptor
	MOV	Word Ptr BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
	AND	AL,7FH
	RET
;
; CHECK	FOR A CTRL-C OR	CTRL-S ENTERED FROM THE	KEYBOARD.  JUMP	TO
; EXIT IF CTRL-C, PAUSE	ON CTRL-S.
;
CKABRT:	MOV	Byte Ptr FUNC,2		;bios console status function
	MOV	Word Ptr BIOS_DESC,CX	;pass disk number to bios descriptor
	MOV	Word Ptr BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
	OR	AL,AL
	JNZ	L_27	
	RET				;NO, RETURN TO CALLER
L_27:
	CALL	CINPUT			;GET CHARACTER
	CMP	AL,'C'-40H		;CTRL-C?
	JNZ	L_28	
	JMP	EXIT			;IF CTRL-C THEN	QUIT
L_28:
	CMP	AL,'S'-40H		;CTRL-S?
	JZ	L_29	
	RET				;NO, RETURN TO CALLER
L_29:
	CALL	CINPUT			;YES, WAIT FOR ANOTHER CHAR.
	CMP	AL,'C'-40H		;MIGHT BE CTRL-C
	JNZ	L_30	
	JMP	EXIT			;EXIT IF CTRL-C, ELSE FALL THRU	AND CONTINUE
L_30:
	RET
;
; ENTRY	TO BDOS	SAVING ALL EXTENDED REGISTERS
;
CPM:	PUSH	CX
	PUSH	DX
	PUSH	BX
	INT	224
	POP	BX
	POP	DX
	POP	CX
	RET
;
; FOR FILE OUTPUT MODE,	RETURN TO OLD USER AREA	AND SET	DMA FOR
; THE FILE OUTPUT BUFFER.
;

	IF	UOPT OR AOPT
SETFOP:
	MOV	AL,Byte Ptr OLDUSR	;GET USER NUMBER AT STARTUP
	MOV	DL,AL
	MOV	CL,CURUSR
	CALL	CPM			;RESET THE OLD USER NUMBER IF CP/M 2
	ENDIF

	IF	FOPT
	MOV	DX,(Offset OUTBUF)	;MOVE DMA FROM SEARCH BUFFER INTO THE
	JMPS	SET2			;..OUTPUT BUFFER
	ENDIF

	RET
;
; MOVE DISK BUFFER DMA TO DEFAULT BUFFER FOR DIRECTORY SEARCH OPERATIONS
; AND BDOS MEDIA CHANGE ROUTINES.
;
SETSRC:	MOV	DX,BASE+80H
SET2:	MOV	CL,SETDMA
	JMPS	CPM
;
; PRINT	THE AMOUNT OF FREE SPACE REMAINING ON THE SELECTED DRIVE
;
PRTFRE:	MOV	BX,Word Ptr FREEBY	;GET SPACE LEFT BEFORE ADDING TO MASTER.DIR
	CALL	DECPRT			;PRINT K FREE
	MOV	DX,(Offset TOTMS6)	;PRINT " FREE."
	JMP	PRINT
;
; COMPARE ROUTINE FOR SORT
;
COMPR:	PUSH	BX			;SAVE TABLE ADDR
	MOV	DL,M			;LOAD LOW ORDER
	INC	BX
	MOV	DH,M			;LOAD HIGH ORDER
	INC	BX
	MOV	CL,M
	INC	BX
	MOV	CH,M
;
; BC, DE NOW POINT TO ENTRIES TO BE COMPARED
;
	XCHG	BX,DX
	MOV	DL,AL			;GET COUNT
CMPLP:	MOV	SI,CX
	MOV	AL,[SI]
	CMP	AL,M
	LAHF
	INC	BX
	SAHF
	LAHF
	INC	CX
	SAHF
	JNZ	NOTEQL			;QUIT ON MISMATCH
	DEC	DL			;OR END	OF COUNT
	JNZ	CMPLP
NOTEQL:	POP	BX
	RET				;COND CODE TELLS ALL
;
; SWAP ENTRIES IN THE ORDER TABLE
;
SWAP:	MOV	CX,(Offset ORDER)-2	;TABLE BASE
	SHL	BX,1			;*2
	LAHF				;+ BASE
	ADD	BX,CX
	SAHF
	XCHG	BX,DX
	SHL	BX,1			;*2
	LAHF				;+ BASE
	ADD	BX,CX
	RCR	SI,1
	SAHF
	RCL	SI,1
	MOV	CL,M
	MOV	SI,DX
	MOV	AL,[SI]
	XCHG	BX,DX
	MOV	M,CL
	MOV	SI,DX
	MOV	[SI],AL
	LAHF
	INC	BX
	SAHF
	LAHF
	INC	DX
	SAHF
	MOV	CL,M
	MOV	SI,DX
	MOV	AL,[SI]
	XCHG	BX,DX
	MOV	M,CL
	MOV	SI,DX
	MOV	[SI],AL
	RET
;
; NEW COMPARE ROUTINE
;
COMPARE:MOV	CX,(Offset ORDER)-2
	SHL	BX,1
	ADD	BX,CX
	XCHG	BX,DX
	SHL	BX,1
	ADD	BX,CX
	XCHG	BX,DX
	MOV	CL,M
	INC	BX
	MOV	CH,M
	XCHG	BX,DX
	MOV	DL,M
	INC	BX
	MOV	DH,M
	XCHG	BX,DX
	MOV	DL,AL			;COUNT
CMPLPE:	MOV	SI,CX
	MOV	AL,[SI]
	CMP	AL,M
	LAHF
	INC	CX
	SAHF
	LAHF
	INC	BX
	SAHF
	JZ	L_31
	RET
L_31:
	DEC	DL
	JNZ	CMPLPE
	RET
;
; ERROR	EXIT
;

	IF	FOPT
ERXIT:
	MOV	AL,0FFH
	MOV	Byte Ptr FOPFLG,AL	;DISABLE FILE OUTPUT MODE ON ERROR
	ENDIF

	CALL	CRLF			;SPACE DOWN
	POP	DX			;GET POINTER TO	MESSAGE	STRING
	CALL	PRINT			;PRINT IT
	MOV	DX,(Offset ERRMS1)	;PRINT " ERROR"
	CALL	PRINT
	CALL	CRLF			;SPACE DOWN
;
; EXIT - ALL DONE, RESTORE STACK
;
EXIT:	MOV	CL,CONST		;CHECK CONSOLE STATUS
	CALL	CPM
	OR	AL,AL			;CHAR WAITING?
	MOV	CL,RDCHR
	JZ	L_32	
	CALL	CPM			;GOBBLE	UP CHAR
L_32:
	MOV	AL,Byte Ptr ROPFLG	;IF DISK SYSTEM	WAS RESET
	OR	AL,AL
	MOV	AL,Byte Ptr OLDDSK	;GET DEFAULT DISK AT STARTUP
	MOV	DL,AL
	MOV	CL,SELDSK		;RESELECT IT
	JNZ	L_33	
	CALL	CPM
L_33:
;
	MOV	CL,0		;BDOS FUNCTION, WARM BOOT
	MOV	DL,0
	INT	224		;RETURN TO CP/M-86
;
; END OF PROGRAM CODE
;
; INITIALIZED DATA AREA
;
DREMSG	DB	'Driv','e' OR 80H
;

	IF	PGPAWZ
EOSMSG	DB	0AH,'Press any key to continue, or CTRL-C to exit',0DH,0AH,0AH,'$'  
	ENDIF

;
ERRMS1	DB	' '
;
ERRMS2	DB	'Erro','r' OR 80H
;

	IF	REPERR
ERRTAG	DB	' -','>' OR 80H
	ENDIF

;
NOFMS1	DB	0DH,0AH,'No such file on Drive '
;
NOFMS2	DB	'  -',' ' OR 80H
;
TOTMS1	DB	0DH,0AH,0DH,0AH,'Drive',' ' OR 80H
;
TOTMS2	DB	', User',' ' OR	80H
;
TOTMS3	DB	' contains',' '	OR 80H
;
TOTMS4	DB	'K in',' ' OR 80H
;
TOTMS5	DB	' files with',' ' OR 80H
;
TOTMS6	DB	'K free',0DH,0AH OR 80H
;
USRMSG	DB	'User ','#' OR 80H
;
FNDFLG	DB	0			;FLAG WHETHER ANY FILES MATCHED
;

	IF	PGPAWZ
LINCNT	DB	0			;COUNT OF LINES	PRINTED	ON SCREEN
	ENDIF

;
; DRIVE	CODE/USER AREA LOOKUP TABLE
; NOTE THAT THE	LODRV-HIDRV TABLE IS INCLUDED HERE FULLY CONFIGURED.
; FOR YOUR OWN USE, YOU	SHOULD CHANGE THE MAXIMUM USER AREAS AS
; APPROPRIATE FOR EACH DRIVE ON	YOUR SYSTEM, AND THEN DELETE ANY
; DBS REFERENCING DRIVES THAT DON'T EXIST.
;
LODRV	EQU	(Offset $)		;MARK BEGINNING	OF DRIVE/USER TABLE
	DB	15			;MAXIMUM USER AREA FOR DRIVE A
	DB	15			;   "	  "    "    "	 "   B
	DB	15			;   "	  "    "    "	 "   C
	DB	15			;   "	  "    "    "	 "   D
	DB	15			;   "	  "    "    "	 "   E
	DB	15			;   "	  "    "    "	 "   F
	DB	15			;   "	  "    "    "	 "   G
	DB	15			;   "	  "    "    "	 "   H
;	DB	15			;   "	  "    "    "	 "   I
;	DB	15			;   "	  "    "    "	 "   J
;	DB	15			;   "	  "    "    "	 "   K
;	DB	15			;   "	  "    "    "	 "   L
;	DB	15			;   "	  "    "    "	 "   M
;	DB	15			;   "	  "    "    "	 "   N
;	DB	15			;   "	  "    "    "	 "   O
;	DB	15			;   "	  "    "    "	 "   P
;
HIDRV	EQU	(Offset $)		;MARK END OF DRIVE/USER	TABLE
;
; OPTION FIELD LOOKUP TABLE.
; NOTE THAT YOU	CAN FORCE ANY OF THESE OPTIONS AS A DEFAULT BY
; CHANGING THE LETTER FOR THE OPTION INTO A ZERO (ASSUMING THAT
; ITS ENABLING EQUATE IS TRUE).	 EACH OPTION THAT YOU HARD-WIRE	IN
; THIS MANNER WILL NO LONGER BE	RECOGNIZED AS A	COMMAND	LINE OPTION,
; AND IF YOU REDUNDANTLY KEY IT IN, XDIR WILL FLAG IT AS UNRECOGNIZED.
;
OTBL	EQU	(Offset $)		;MARK START OF OPTION TABLE
;
;ALL USERS-OPTION FLAG
;

	IF	AOPT
AOPFLG	DB	'A'
	ENDIF

;
;MULTI-DISK-OPTION FLAG
;

	IF	DOPT
DOPFLG	DB	'D'
	ENDIF

;
;FILE-OUTPUT-OPTION FLAG
;

	IF	FOPT
FOPFLG	DB	'F'
	ENDIF

;
;NO PAGE-PAUSE OPTION FLAG
;

	IF	NOPT AND PGPAWZ
NOPFLG	DB	'N'
	ENDIF

;
;PRINTER OPTION	FLAG
;

	IF	POPT
POPFLG	DB	'P'
	ENDIF

;
;RESET OPTION FLAG
;
ROPFLG	DB	'R'
;
;SYSTEM	FILE OPTION FLAG
;

	IF	SOPT
SOPFLG	DB	'S'
;
	ENDIF

OEND	EQU	(Offset $)		;MARK END OF OPTION TABLE
;
; END OF OPTION	LOOKUP TABLE
;

	IF	FOPT
BUFPNT	DW	(Offset OUTBUF)		;POINTER TO NEXT LOCATION IN OUTPUT BUFFER
BUFCNT	DB	128			;NUMBER OF BYTES LEFT IN OUTPUT BUFFER
OPNFLG	DB	0			;FILE OPEN FLAG FOR ALL USER FILE OUTPUT
;
OUTFCB	DB	0,'XDIR    DIR'
	DB	0			;REST OF XDIR.DIR FILE CONTROL BLOCK
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
	DB	0
;
OUTBUF	RS	128			;OUTPUT	FILE BUFFER
	ENDIF

;
; UNINITIALIZED	DATA AREA
;
;storage for 5 byte BIOS function descriptor
;
FUNC		RS	1		;bios function code goes here
BIOS_DESC	RS	2		;[CX] data goes here
		RS	2		;[DX] data goes here
;
BASUSR	RS	1			;DUPE OF ORIGINAL DIRECTORY USER # TO SEARCH
BLKMAX	RS	2			;HIGHEST BLOCK # ON DRIVE
BLKMSK	RS	1			;SEC/BLK - 1
BLKSHF	RS	1			;# SHIFTS TO MULT BY SEC/BLK
COUNT	RS	2			;ENTRY COUNT
dsksiz	RS	2			;HIGHEST FILE #	IN DIRECTORY
FREEBY	RS	2			;CONTAINS NUMBER OF K LEFT ON DIRECTORY	DRIVE
GAP	RS	2			;SORT ROUTINE STORAGE
I	RS	2			;SORT ROUTINE STORAGE
J	RS	2			;SORT ROUTINE STORAGE
JGAP	RS	2			;SORT ROUTINE STORAGE
LZFLG	RS	1			;0 WHEN	PRINTING LEADING ZEROS
MAXUSR	RS	1			;MAXIMUM USER #	FOR DRIVE FROM LOOKUP TABLE
NEWUSR	RS	1			;CONTAINS USER NUMBER SELECTED BY "$U" OPTION
NEXTT	RS	2			;NEXT TABLE ENTRY
OLDDSK	RS	1			;HOLDER	FOR CURRENTLY LOGGED-IN	DRIVE
OLDUSR	RS	1			;CONTAINS USER NUMBER UPON INVOCATION
SCOUNT	RS	2			;# TO SORT
SUPSPC	RS	1			;LEADING SPACE FLAG FOR	DECIMAL	ROUTINE
TBLOC	RS	2			;POINTER TO START OF NAME TABLE
TEMP	RS	2			;SAVE DIR ENTRY
TOTFIL	RS	2			;TOTAL NUMBER OF FILES
TOTSIZ	RS	2			;TOTAL SIZE OF ALL FILES
ORDER	EQU	(Offset $)		;ORDER TABLE STARTS HERE
;
	RS	4000H
	DB	0
;
	END
